package nio;

import org.junit.Test;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.charset.CharacterCodingException;
import java.nio.charset.Charset;
import java.nio.charset.CharsetDecoder;
import java.nio.charset.CharsetEncoder;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;

/**
 * 一、通道（Channel）：用于源节点与目标节点的连接。在 Java NIO 中负责缓冲区中数据的传输。 Channel 本身不存储数据，因此需要配合缓冲区进行传输。
 * <p>
 * 二、通道的主要实现类
 * java.nio.channels.Channel 接口：
 * |- FileChannel
 * |- SocketChannel
 * |- ServerSocketChannel
 * |- DatagramChannel
 * <p>
 * 三、获取通道
 * 1. Java 针对支持通道的类提供了 getChannel() 方法
 * 本地IO： FileInputStream、FileOutputStream、RandomAccessFile
 * 网络IO： Socket、ServerSocket、DatagramSocket
 * 2. 在 JDK 1.7 中的 NIO.2 针对各个通道提供了静态方法 open()
 * 3. 在 JDK 1.7 中的 NIO.2 的 Files 工具类的 newByteChannel()
 * <p>
 * 四、通道之间的数据传输
 * transferTo()
 * transferFrom()
 * 五、分散（Scatter）与聚集（Gather）
 * 分散读取： 将通道中的数据分散到多个缓冲区中
 * 聚集写入： 将多个缓冲区中的数据聚集到通道中
 * 六、字符集： Charset
 * 编码： 字符串 -> 字节数组
 * 解码： 字节数组 -> 字符串
 */
public class ChannelTest {
    @Test
    public void test5() throws CharacterCodingException {
        Charset charset = Charset.forName("GBK");
        // 获取编码器
        CharsetEncoder encoder = charset.newEncoder();
        // 获取解码器
        CharsetDecoder decoder = charset.newDecoder();

        CharBuffer cBuf = CharBuffer.allocate(1024);
        cBuf.put("什码情况");
        cBuf.flip();
        // 编码
        ByteBuffer bBuf = encoder.encode(cBuf);
        for (int i = 0; i < bBuf.limit(); i++) {
            System.out.println(bBuf.get());
        }

        // 解码
        bBuf.flip();
        CharBuffer cBuf2 = decoder.decode(bBuf);
        System.out.println(cBuf2.toString());
    }

    // 分散和聚集
    @Test
    public void test4() throws Exception {
        RandomAccessFile raf = new RandomAccessFile("1.txt", "rw");
        // 1、获取通道
        FileChannel channel = raf.getChannel();

        // 2、分配指定大小的缓冲区
        ByteBuffer buf1 = ByteBuffer.allocate(100);
        ByteBuffer buf2 = ByteBuffer.allocate(1024);

        // 3、分散读取
        ByteBuffer[] bufs = {buf1, buf2};
        channel.read(bufs);

        for (ByteBuffer buf : bufs) {
            buf.flip();
        }

        System.out.println(new String(bufs[0].array(), 0, bufs[0].limit()));
        System.out.println("----------------------------------------------------");
        System.out.println(new String(bufs[1].array(), 0, bufs[1].limit()));

        // 4、聚集写入
        RandomAccessFile raf2 = new RandomAccessFile("2.txt", "rw");
        FileChannel channel2 = raf2.getChannel();
        channel2.write(bufs);

        raf.close();
        raf2.close();
        channel.close();
        channel2.close();
    }


    // 通道之间的数据传输（直接缓冲区）
    @Test
    public void test3() throws Exception {
        FileChannel inChannel = FileChannel.open(Paths.get("1.jpg"), StandardOpenOption.READ);
        FileChannel outChannel = FileChannel.open(Paths.get("1_cp.jpg"), StandardOpenOption.READ, StandardOpenOption.WRITE, StandardOpenOption.CREATE);

        inChannel.transferTo(0, inChannel.size(), outChannel);
//        outChannel.transferFrom(inChannel, 0, inChannel.size());

        inChannel.close();
        outChannel.close();
    }

    // 使用直接缓冲区完成文件的复制（内存映射文件）
    @Test
    public void testNioCp2() throws Exception {
        FileChannel inChannel = FileChannel.open(Paths.get("1.jpg"), StandardOpenOption.READ);
        FileChannel outChannel = FileChannel.open(Paths.get("1_cp.jpg"), StandardOpenOption.READ, StandardOpenOption.WRITE, StandardOpenOption.CREATE);

        // 内存映射文件
        MappedByteBuffer inMappedBuf = inChannel.map(FileChannel.MapMode.READ_ONLY, 0, inChannel.size());
        MappedByteBuffer outMappedBuf = outChannel.map(FileChannel.MapMode.READ_WRITE, 0, inChannel.size());

        // 直接对缓冲区进行数据的读写操作
        byte[] dst = new byte[inMappedBuf.limit()];
        System.out.println("limit: " + inMappedBuf.limit());
        inMappedBuf.get(dst);
        outMappedBuf.put(dst);

        inChannel.close();
        outChannel.close();
    }

    // 利用通道完成文件复制
    @Test
    public void testNioCopy() throws Exception {
        FileInputStream fis = new FileInputStream("1.jpg");
        FileOutputStream fos = new FileOutputStream("1_cp.jpg");

        // 1. 获取通道
        FileChannel inChannel = fis.getChannel();
        FileChannel outChannel = fos.getChannel();

        // 2. 分配指定大小的缓冲区
        ByteBuffer buf = ByteBuffer.allocate(1024);      // 非直接缓冲区
        // ByteBuffer buf = ByteBuffer.allocateDirect(1024);   // 直接缓冲区

        // 3. 将通道中的数据存入缓冲区
        while (inChannel.read(buf) != -1) {
            // 切换到读模式
            buf.flip();
            // 4. 将缓冲区中的数据写入通道中
            outChannel.write(buf);
            // 清空缓冲区
            buf.clear();
        }

        // 关闭对应的IO
        outChannel.close();
        inChannel.close();
        fos.close();
        fis.close();
    }
}
